package io.summer.auth.tool;

import cn.hutool.core.io.FileUtil;
import cn.hutool.crypto.CipherMode;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Objects;

/**
 * @author ml
 */
@Slf4j
public abstract class RsaTool {
  private RsaTool() {
  }

  private static final int DEFAULT_KEY_SIZE = 512;

  /**
   * 获取公钥
   *
   * @param filename
   * @return
   * @throws InvalidKeySpecException
   * @throws NoSuchAlgorithmException
   */
  public static PublicKey getPublicKey(String filename) throws InvalidKeySpecException, NoSuchAlgorithmException {
    byte[] bytes = FileUtil.readBytes(filename);
    return getPublicKey(bytes);
  }

  public static String encrpyt(String data, Key key) {
    Objects.requireNonNull(data);
    byte[] encryptData = encrypt(data.getBytes(), key);
    return new String(encryptData);
  }

  public static byte[] encrypt(byte[] data, Key key) {
    try {
      Cipher cipher = Cipher.getInstance("RSA");
      cipher.init(Cipher.ENCRYPT_MODE, key);
      return cipher.doFinal(data);
    } catch (NoSuchAlgorithmException
      | NoSuchPaddingException
      | InvalidKeyException
      | IllegalBlockSizeException
      | BadPaddingException e) {
      log.error("encrypt failed: ", e);
    }
    return data;
  }

  public static String decrypt(String encryptData, Key key) {
    byte[] decryptData = decrypt(encryptData.getBytes(), key);
    return new String(decryptData);
  }

  public static byte[] decrypt(byte[] encryptData, Key key) {
    try {
      Cipher cipher = Cipher.getInstance("RSA");
      cipher.init(Cipher.DECRYPT_MODE, key);
      return cipher.doFinal(encryptData);
    } catch (NoSuchAlgorithmException
      | NoSuchPaddingException
      | InvalidKeyException
      | IllegalBlockSizeException
      | BadPaddingException e) {
      log.error("decrypt failed: ", e);
    }
    return encryptData;
  }

  /**
   * 读取私钥
   *
   * @param filename 文件路径
   * @return
   * @throws InvalidKeySpecException
   * @throws NoSuchAlgorithmException
   */
  public static PrivateKey getPrivateKey(String filename) throws InvalidKeySpecException, NoSuchAlgorithmException {
    byte[] bytes = FileUtil.readBytes(filename);
    return getPrivateKey(bytes);
  }

  private static PublicKey getPublicKey(byte[] bytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
    bytes = Base64.getDecoder().decode(bytes);
    X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
    KeyFactory factory = KeyFactory.getInstance("RSA");
    return factory.generatePublic(spec);
  }

  private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
    bytes = Base64.getDecoder().decode(bytes);
    PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
    KeyFactory factory = KeyFactory.getInstance("RSA");
    return factory.generatePrivate(spec);
  }

  /**
   * 根据密文，生存rsa公钥和私钥,并写入指定文件
   *
   * @param publicKeyFilename  公钥文件路径
   * @param privateKeyFilename 私钥文件路径
   * @param secret             生成密钥的密文
   */
  public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws NoSuchAlgorithmException {
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
    SecureRandom secureRandom = new SecureRandom(secret.getBytes());
    keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
    KeyPair keyPair = keyPairGenerator.genKeyPair();
    byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
    publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
    FileUtil.writeBytes(publicKeyBytes, publicKeyFilename);
    byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
    privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
    FileUtil.writeBytes(privateKeyBytes, privateKeyFilename);
  }
}
